/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1/commercial
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may use this file in compliance with the License.
 * Alternatively, if you can't copy with the License, namely if you do not
 * want to provide source code of your modifications to this file, you may
 * negotiate a commercial licence with the initial developer.
 * You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is COID.
 *
 * The Initial Developer of the Original Code is
 * PosAm.
 * Portions created by the Initial Developer are Copyright (C) 2003
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 * Brano Kemen
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */


#ifndef __COID_SERVER_CMDIN__HEADER_FILE__
#define __COID_SERVER_CMDIN__HEADER_FILE__

#include <stdarg.h>

#include "coid/comm/retcodes.h"
#include "coid/comm/str.h"
#include "coid/comm/net.h"
#include "coid/comm/password.h"
#include "coid/comm/version.h"
#include "coid/comm/metastream/metastream.h"

//#include "interfnc.h"
#include "logstream.h"
#include "coid/coid.h"

namespace coid {


struct InstanceData;

////////////////////////////////////////////////////////////////////////////////
/// Service descriptor
struct ServiceDescriptor
{
    //static const char* class_name()     { return "ServiceDescriptor"; }

    version     _version;           ///< service version
    version     _coid_version;      ///< coid server version
    charstr     _name;              ///< service name, same as the class name
    charstr     _shortname;         ///< short service name, max 7 chars, for console prints
    charstr     _info;              ///< info text about service
    const char**_dependencies;      ///< list of services that this service depends on, null ptr terminated


    const version& get_version(const void*) const      { return _version; }
    const char* get_class_name(const void*) const      { return _name.ptr(); }
    
    ///Method descriptor
    struct MethodDesc
    {
        struct Arg
        {
            charstr name;           ///< argument name
            charstr type;           ///< argument type as declared
            charstr meta;           ///< meta type

            Arg() { }
            Arg( token var )
            {
                name = var.cut_right_back(' ');
                type = var;
            }
        };

        charstr name;
		Arg retval;
        dynarray<Arg> inargs;
        dynarray<Arg> outargs;
        uint ID;
        uint flags;

        /// method flags
        enum {
            fCONST                      = 0x01,     ///< the method doesn't modify the object for which it's executed

            fLOG_CALL                   = 0x0100,   ///< log the calls of this method
            fLOG_DURATION               = 0x0200,   ///< if also the fLOG_CALL enabled, measure and log the call duration

            fMETA                       = 0x1000,
            fNO_LOCK                    = 0x2000,

            fACCESS_MODE_DIRECT         = 0x010000,
            fACCESS_MODE_INTERPROCESS   = 0x020000,
            fACCESS_MODE_REMOTE         = 0x040000,
            xACCESS_MODE                = 0x070000,
        };

		MethodDesc() {}
		MethodDesc( const char* n, const char* ret, const char** in, const char** out, uint id, uint flg ) :
			name(n), retval(ret), ID(id), flags(flg)
		{
            {for( uint i=0; in[i]!=0; ++i )  *inargs.add() = token(in[i]);}
            {for( uint i=0; out[i]!=0; ++i )  *outargs.add() = token(out[i]);}
        }
		~MethodDesc() {}
    };


    uint        _nmeth;             ///< method count
    const MethodDesc* _methflags;   ///< method flags, see enum

    uint        _flags;             ///< service flags, see enum
    uint        _acceptor_port;     ///< port number that should be used by a specialized
                                    ///< service acceptor. The acceptor will catch connection requests,
                                    ///< upon a connection it creates new service instance and calls
                                    ///< its StdDispatchMethod::HANDLE_NET_LOOP.
                                    ///< Used for special protocol handling services.

    /// service properties
    enum {
        fAUTONOMOUS         = 0x01, ///< the service can be autonomously instantiated by a client,
                                    ///<  otherwise it can only be open from instance of a service
                                    ///<  into an existing channel
        fBINDABLE           = 0x02, ///< service instance can be bound to already running object
        fSHARED             = 0x04, ///< clients are attempted to connect to already running instances first,
                                    ///<  if it fails, a new instance is created
        fHAS_SPAWN_POINT    = 0x08, ///< the service has a method that should be executed after creation,
                                    ///<  a separate thread is created for the execution
        fAUTORUN            = 0x10, ///< this service can have automatic instance created by the server
                                    ///<  the instances will be authentified by the AUTHENTIFY_AUTO method
        fACCEPTOR_BOUND_SESSION = 0x20, ///< valid only if _acceptor_port is set (!UMAX)
                                    ///< forces the specialized service acceptor to keep one service
                                    ///< instance with each its working thread, and handle the connection
                                    ///< requests through it, instead of creating new instance upon
                                    ///< new connection. Used for sessionless protocols.

        xLOG                        = 0x0300,
        fLOG_CALL                   = 0x0100, ///< log the calls of this method
        fLOG_DURATION               = 0x0200, ///< if also the fLOG_CALL enabled, measure and log the call duration

        fALLOW_DIRECT_ACCESS        = 0x010000,
        fALLOW_INTERPROCESS_ACCESS  = 0x020000,
        fALLOW_REMOTE_ACCESS        = 0x040000,

        fDEBUG                      = 0x80000000,   ///< debug version
    };

	const char * _bin_svc_data;     ///< binary data of the service (functions, argument types, ...)
    metastream& (*_fnc_get_meta)(); ///< function to get metastream with type descriptors for given service


    void check_access( uint& connflags ) const
    {
        if( (_flags & fALLOW_DIRECT_ACCESS) == 0 )
            connflags &= ~StdProtocolMethod::ConnectFlags::fACCESS_MODE_DIRECT;

        if( (_flags & fALLOW_INTERPROCESS_ACCESS) == 0 )
            connflags &= ~StdProtocolMethod::ConnectFlags::fACCESS_MODE_INTERPROCESS;

        if( (_flags & fALLOW_REMOTE_ACCESS) == 0 )
            connflags &= ~StdProtocolMethod::ConnectFlags::fACCESS_MODE_REMOTE;
    }


    /// return method id given the name
	typedef uint (* f_get_method_id)( const token& );
    uint (* get_method_id)( const token& );

    /// dispatch call
	typedef opcd (* f_dispatch)( InstanceData*, uint method, binstream& );
    opcd (* dispatch)( InstanceData*, uint method, binstream&, metastream* );


    ////////////////////////////////////////////////////////////////////////////////
    uint is_implemented( uint meth ) const
    { return meth < _nmeth  ?  (_methflags[meth].flags & MethodDesc::xACCESS_MODE)  :  0; }

    uint is_const( uint meth ) const
    { return meth < _nmeth  ?  (_methflags[meth].flags & MethodDesc::fCONST)  :  0; }

    uint is_unlocked( uint meth ) const
    { return meth < _nmeth  ?  (_methflags[meth].flags & MethodDesc::fNO_LOCK)  :  0; }

    uint get_flags( uint meth ) const
    { uint f = _flags & xLOG;  if( meth < _nmeth )  f &= _methflags[meth].flags;  return f; }
};


inline binstream& operator << (binstream& out, const ServiceDescriptor::MethodDesc::Arg& ma)
{	out << ma.name << ma.type << ma.meta;  return out; }
inline binstream& operator >> (binstream& in, ServiceDescriptor::MethodDesc::Arg& ma)
{	in >> ma.name >> ma.type >> ma.meta;  return in; }
inline metastream& operator << (metastream& m, const ServiceDescriptor::MethodDesc::Arg& ma)
{
    MSTRUCT_OPEN(m,"ServiceDescriptor::MethodDesc::Arg");
    MM(m,"name",ma.name);
    MM(m,"type",ma.type);
    MM(m,"meta",ma.meta);
    MSTRUCT_CLOSE(m);
}

inline binstream& operator << (binstream& out, const ServiceDescriptor::MethodDesc& mi)
{   out << mi.name << mi.ID << mi.flags << mi.retval << mi.inargs << mi.outargs; return out; }
inline binstream& operator >> (binstream& in, ServiceDescriptor::MethodDesc& mi)
{	in >> mi.name >> mi.ID >> mi.flags >> mi.retval >> mi.inargs >> mi.outargs;  return in; }
inline metastream& operator << (metastream& m, const ServiceDescriptor::MethodDesc& mi)
{
    MSTRUCT_OPEN(m,"ServiceDescriptor::MethodDesc");
    MM(m,"name",mi.name);
    MM(m,"id",mi.ID);
    MM(m,"flags",mi.flags);
    MM(m,"retval",mi.retval);
    MM(m,"inargs",mi.inargs);
    MM(m,"outargs",mi.outargs);
    MSTRUCT_CLOSE(m);
}


////////////////////////////////////////////////////////////////////////////////
struct LOGMSG
{
    enum {
        // std
        TYPE_ALERT              = 1,    // server must be stopped
        TYPE_CRIT               = 2,    // critical error (server side...)
        TYPE_ERROR              = 3,    // std error (bad client request...)
        TYPE_WARNING            = 4,    // warning
        TYPE_NOTICE             = 5,    // notice
        TYPE_INFO               = 6,    // info

        // debug
        TYPE_DEBUG              = 7,    // debug (DEBUG())
        TYPE_TRACE              = 8,    // trace (TRACE())

        // system
        TYPE_OPEN               = 9,
        TYPE_CLOSE              = 10,
        TYPE_TERM               = 11,
        TYPE_ATTACH             = 12,
        TYPE_DETACH             = 13,

        TYPE_METHOD             = 14,

        TYPE_MESSAGE            = 15,
    };
};

////////////////////////////////////////////////////////////////////////////////
typedef void *(*t_thread_fnc) (void *);


////////////////////////////////////////////////////////////////////////////////
#define EOM 	BINSTREAM_FLUSH

struct conn_info
{
    charstr client_addr;
};

////////////////////////////////////////////////////////////////////////////////
struct cmd_interface_vtbl
{
    typedef cmd_interface_vtbl T;

    // locking
    void (T::*mutex_wrlock)();
    void (T::*mutex_rdlock)();
    void (T::*mutex_unlock)();

    //LOG
    binstream& (T::*set_msg) ( uchar msgclass, uchar verb );

    uint    (T::*get_id)() const;

    token   (T::*short_name)() const;
    token   (T::*class_name)() const;

    opcd    (T::*get_name) ( charstr& name ) const;
    opcd    (T::*set_name) ( const token& name );

    opcd    (T::*set_option) ( uint optid, int value );
    opcd    (T::*get_option) ( uint optid, int* value ) const;

    opcd    (T::*set_method_flags)( uint obj, uint method, uint flags );

    //opcd    (T::*attach_service) ( const token& service, const version& ver, uint& sid, binstream* arg );

    opcd    (T::*dispatch_method)( uint obj, const token& method, binstream& parinout, metastream* meta );

    opcd    (T::*spawn_thread)( t_thread_fnc start, void* arg, const char* name );
    opcd    (T::*free_threads)();

    opcd    (T::*parse_path) ( uint id, const token& tok, dynarray<uint>& out ) const;
    charstr&(T::*get_server_address) ( charstr& buf ) const;

    uint    (T::*get_superior_id)() const;

    opcd    (T::*list_devices) ( const token& classname, const version& ver, const token& name, dynarray<uint>& devices ) const;
    opcd    (T::*list_conn_devices) ( const token& classname, const version& ver, const token& name, dynarray<uint>& devices ) const;

    //opcd    (T::*get_device_interface) ( uint id, const token& svcname, const version& clientver, binstream& arg );

    opcd    (T::*get_device_info)      ( uint id, charstr& service, version& ver, charstr& name ) const;
    char*   (T::*get_working_dir)     ( charstr& buf ) const;

    opcd    (T::*find_nodes_of_class)  ( uint from, const charstr& name, const token& service, const version& ver, dynarray<uint>& lst ) const;

    opcd    (T::*get_conn_info)  ( charstr& out ) const;

    opcd    (T::*get_object_address) ( netaddr& addr, uint* obj ) const;

    netAddress* (T::*get_server_address_nl) ( netAddress* buf ) const;

    opcd    (T::*get_method_desc) ( uint id, const ServiceDescriptor::MethodDesc** pdesc, uint* nmeth ) const;

    opcd    (T::*set_identity) ( account_id& acid, bool create );

    bool    (T::*is_exit_signalled) () const;

	opcd	(T::*open_service) ( COID_OPEN_SERVICE & cos );

    uint    (T::*get_connection_id)() const;

    opcd    (T::*request_autodestroy)( uint secfwd );
};

////////////////////////////////////////////////////////////////////////////////
#define CMDIN_GUARD(pcmdin)         cmd_interface::GUARD __guard__(pcmdin)
#define CMDIN_GUARD_WR(pcmdin)      cmd_interface::GUARD __guard__(pcmdin)
#define CMDIN_GUARD_RD(pcmdin)      cmd_interface::GUARD __guard__(pcmdin)

////////////////////////////////////////////////////////////////////////////////
struct cmd_interface
{
    cmd_interface()
    {
        error = 0;
        _me = 0;
        _vtbl = 0;
    }

    void init( cmd_interface_vtbl* vtbl, void* him )
    {
        error = 0;
        _me = (cmd_interface_vtbl*)him;
        _vtbl = vtbl;
    }

    // locking
    void mutex_wrlock()     { (_me->*(_vtbl->mutex_wrlock)) (); }
    void mutex_rdlock()     { (_me->*(_vtbl->mutex_rdlock)) (); }
    void mutex_unlock()     { (_me->*(_vtbl->mutex_unlock)) (); }

    struct GUARD
    {
        GUARD( const cmd_interface& ifc ) : _ifc((cmd_interface&)ifc)   { _ifc.mutex_rdlock(); }
        GUARD( const cmd_interface* ifc ) : _ifc(*(cmd_interface*)ifc)  { _ifc.mutex_rdlock(); }
        GUARD( cmd_interface& ifc ) : _ifc(ifc)         { _ifc.mutex_wrlock(); }
        GUARD( cmd_interface* ifc ) : _ifc(*ifc)        { _ifc.mutex_wrlock(); }

        GUARD( cmd_interface* ifc, int wr ) : _ifc(*ifc)
        {
            if(wr)
                _ifc.mutex_wrlock();
            else
                _ifc.mutex_rdlock();
        }

        ~GUARD()                                        { _ifc.mutex_unlock(); }

    protected:
        cmd_interface& _ifc;
    };

    //LOG
    binstream& set_msg( uchar msgclass, uchar verb )
    {       return (_me->*(_vtbl->set_msg)) ( msgclass, verb ); }


    void mprintf( uchar msgclass, const char* format, ... )
    {
        static char buffer[1024];
        va_list argptr;
        va_start( argptr, format );
#ifdef SYSTYPE_MSVC8plus
        vsprintf_s( buffer, 1024, format, argptr );
#else
        vsprintf( buffer, format, argptr );
#endif
        va_end( argptr );

        set_msg( msgclass, 0 ) << buffer;
    }

    binstream& post_alert(uchar verbose)        { return (_me->*(_vtbl->set_msg)) ( LOGMSG::TYPE_ALERT, verbose); }
    binstream& post_critical(uchar verbose)     { return (_me->*(_vtbl->set_msg)) ( LOGMSG::TYPE_CRIT, verbose); }
    binstream& post_error(uchar verbose)        { return (_me->*(_vtbl->set_msg)) ( LOGMSG::TYPE_ERROR, verbose); }
    binstream& post_warning(uchar verbose)      { return (_me->*(_vtbl->set_msg)) ( LOGMSG::TYPE_WARNING, verbose); }
    binstream& post_info(uchar verbose)         { return (_me->*(_vtbl->set_msg)) ( LOGMSG::TYPE_INFO, verbose); }
    //connection related
    binstream& post_open()                      { return (_me->*(_vtbl->set_msg)) ( LOGMSG::TYPE_OPEN, 0); }
    binstream& post_close()                     { return (_me->*(_vtbl->set_msg)) ( LOGMSG::TYPE_CLOSE, 0); }
    binstream& post_term()                      { return (_me->*(_vtbl->set_msg)) ( LOGMSG::TYPE_TERM, 0); }
    //server object related
    binstream& post_attach(uchar verbose)       { return (_me->*(_vtbl->set_msg)) ( LOGMSG::TYPE_ATTACH, verbose); }
    binstream& post_detach(uchar verbose)       { return (_me->*(_vtbl->set_msg)) ( LOGMSG::TYPE_DETACH, verbose); }
    //debug
    binstream& post_debug(uchar verbose)        { return (_me->*(_vtbl->set_msg)) ( LOGMSG::TYPE_DEBUG, verbose); }
    binstream& post_trace(uchar verbose)        { return (_me->*(_vtbl->set_msg)) ( LOGMSG::TYPE_TRACE, verbose); }

    binstream& post_method()                    { return (_me->*(_vtbl->set_msg)) ( LOGMSG::TYPE_METHOD, 3); }
    binstream& post_console_msg()               { return (_me->*(_vtbl->set_msg)) ( LOGMSG::TYPE_MESSAGE, 0); }

    uint get_id() const
    {       return (_me->*(_vtbl->get_id)) (); }

    uint get_connection_id() const
    {       return (_me->*(_vtbl->get_connection_id)) (); }

    token short_name() const
    {       return (_me->*(_vtbl->short_name)) (); }

    token class_name() const
    {       return (_me->*(_vtbl->class_name)) (); }   

    opcd get_name( charstr& name ) const
    {       return (_me->*(_vtbl->get_name)) ( name ); }

    opcd set_name( const token& name )
    {       return (_me->*(_vtbl->set_name)) ( name ); }

    enum {
        OPTION_ALLOW_SHARED_ACCESS          = 1,
        OPTION_LOG_CALLS                    = 2,
    };

    opcd set_option ( uint optid, int value )
    {       return (_me->*(_vtbl->set_option)) ( optid, value ); }

    opcd get_option ( uint optid, int* value ) const
    {       return (_me->*(_vtbl->get_option)) ( optid, value ); }

    bool is_exit_signalled() const
    {       return (_me->*(_vtbl->is_exit_signalled)) (); }

    opcd set_method_flags( uint obj, uint method, uint flags )
    {       return (_me->*(_vtbl->set_method_flags)) ( obj, method, flags ); }

    //opcd attach_service( const token& service, const version& ver, uint& sid, binstream* arg )
    //{       return (_me->*(_vtbl->attach_service)) ( service, ver, sid, arg ); }

    opcd dispatch_method( uint obj, const token& method, binstream& parinout, metastream* meta )
    {       return (_me->*(_vtbl->dispatch_method)) ( obj, method, parinout, meta ); }

    opcd spawn_thread( t_thread_fnc start, void* arg, const char* name )
    {       return (_me->*(_vtbl->spawn_thread)) ( start, arg, name ); }

    opcd free_threads()
    {       return (_me->*(_vtbl->free_threads)) (); }

    opcd parse_path( uint id, const token& tok, dynarray<uint>& out ) const
    {       return (_me->*(_vtbl->parse_path)) ( id, tok, out ); }

    charstr& get_server_address( charstr& buf ) const
    {       return (_me->*(_vtbl->get_server_address)) ( buf ); }

    uint get_superior_id() const
    {       return (_me->*(_vtbl->get_superior_id)) (); }

    opcd list_devices( const token& classname, const version& ver, const token& name, dynarray<uint>& devices ) const
    {       return (_me->*(_vtbl->list_devices)) ( classname, ver, name, devices ); }

    opcd list_conn_devices( const token& classname, const version& ver, const token& name, dynarray<uint>& devices ) const
    {       return (_me->*(_vtbl->list_conn_devices)) ( classname, ver, name, devices ); }

    opcd get_device_info( uint id, charstr& service, version& ver, charstr& name ) const
    {       return (_me->*(_vtbl->get_device_info)) ( id, service, ver, name ); }

    char* get_working_dir( charstr& buf ) const
    {       return (_me->*(_vtbl->get_working_dir)) ( buf ); }

    charstr append_to_cwd( const token& path ) const
    {
        charstr ret;
        get_working_dir(ret);
        ret << path;
        return ret;
    }

    charstr& get_device_info_string( charstr& buf, uint id=UMAX32 ) const
    {
        charstr name,v;
        version ver;

        opcd e = get_device_info( id, buf, ver, name );
        if(e)
            buf << opcd_formatter(e);
        else
            buf << "-" << ver.get_version(v) << ":" << name;
        return buf;
    }

    opcd find_nodes_of_class( uint from, const charstr& name, const token& service, const version& ver, dynarray<uint>& lst ) const
    {       return (_me->*(_vtbl->find_nodes_of_class)) ( from, name, service, ver, lst ); }

    opcd get_conn_info( charstr& out ) const
    {       return (_me->*(_vtbl->get_conn_info)) ( out ); }

    opcd get_object_address( netaddr& addr, uint* obj ) const
    {       return (_me->*(_vtbl->get_object_address)) ( addr, obj ); }

    netAddress* get_server_address_nl( netAddress* buf ) const
    {       return (_me->*(_vtbl->get_server_address_nl)) ( buf ); }

    opcd get_method_desc( uint id, const ServiceDescriptor::MethodDesc** pdesc, uint* nmeth ) const
    {       return (_me->*(_vtbl->get_method_desc)) ( id, pdesc, nmeth ); }

    opcd set_identity( account_id& acid, bool create=false )
    {       return (_me->*(_vtbl->set_identity)) ( acid, create );    }


    opcd open_service( COID_OPEN_SERVICE & cos )
    {       return (_me->*(_vtbl->open_service)) ( cos );    }

    opcd request_autodestroy( uint secfwd )
    {       return (_me->*(_vtbl->request_autodestroy)) ( secfwd ); }


public:
	opcd error;     // modify to indicate that error occured in your function.
                    // it's cleared before every call, and if it's non-zero after your method returns, output parameters are not streamed

	opcd get_error() {return error;}
	void set_error( opcd e ) {error = e;}

private:
    cmd_interface_vtbl* _vtbl;
    cmd_interface_vtbl* _me;
};

} // namespace coid


#ifdef SYSTYPE_MSVC
#pragma warning( default : 4731 )
#pragma warning( default : 4035 )
#endif //SYSTYPE_MSVC


#endif //__COID_SERVER_CMDIN__HEADER_FILE__
